using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using Microsoft.Office.Server.Search.Administration;
using Winsmarts.SSPC.Common;
using Winsmarts.SSPC.CrawlProperties.BO;

namespace Winsmarts.SSPC.ManagedProperties.BO
{
    [XmlRoot("ManagedProperty")]
    public class ManagedPropertyInfo
    {
        private string name;
        private string managedType;
        private string description;
        private bool enabledForScoping;
        private bool fullTextQueriable;
        private bool nameNormalized;
        private bool noWordBreaker;
        private bool removeDuplicates;
        private bool respectPriority;
        private bool retrievable;

        private List<CrawledPropertyInfo> crawledProperties = new List<CrawledPropertyInfo>();

        [XmlAttribute]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [XmlAttribute]
        public string Managedtype
        {
            get { return managedType; }
            set { managedType = value; }
        }

        public List<CrawledPropertyInfo> CrawledProperties
        {
            get { return crawledProperties; }
            set { crawledProperties = value; }
        }

        [XmlAttribute]
        public string Description
        {
            get { return description; }
            set { description = value; }
        }

        [XmlAttribute]
        public bool EnabledForScoping
        {
            get { return enabledForScoping; }
            set { enabledForScoping = value; }
        }

        [XmlAttribute]
        public bool FullTextQueriable
        {
            get { return fullTextQueriable; }
            set { fullTextQueriable = value; }
        }

        [XmlAttribute]
        public bool NameNormalized
        {
            get { return nameNormalized; }
            set { nameNormalized = value; }
        }

        [XmlAttribute]
        public bool NoWordBreaker
        {
            get { return noWordBreaker; }
            set { noWordBreaker = value; }
        }

        [XmlAttribute]
        public bool RemoveDuplicates
        {
            get { return removeDuplicates; }
            set { removeDuplicates = value; }
        }

        [XmlAttribute]
        public bool RespectPriority
        {
            get { return respectPriority; }
            set { respectPriority = value; }
        }

        [XmlAttribute]
        public bool Retrievable
        {
            get { return retrievable; }
            set { retrievable = value; }
        }

        public ManagedPropertyInfo() { }

        public ManagedPropertyInfo(ManagedProperty theProperty)
        {
            name = theProperty.Name;
            managedType = theProperty.ManagedType.ToString();
            description = theProperty.Description;
            enabledForScoping = theProperty.EnabledForScoping;
            fullTextQueriable = theProperty.FullTextQueriable;
            nameNormalized = theProperty.NameNormalized;
            noWordBreaker = theProperty.NoWordBreaker;
            removeDuplicates = theProperty.RemoveDuplicates;
            respectPriority = theProperty.RespectPriority;
            retrievable = theProperty.Retrievable;

            
            //The mapped crawled properties
            foreach(Mapping mapping in theProperty.GetMappings())
            {               
                crawledProperties.Add( new CrawledPropertyInfo(mapping.CrawledPropertyName, mapping.CrawledPropertyVariantType, mapping.CrawledPropset));
            }
        }

        public void SetManagedProperties(ManagedProperty managedProperty, Schema schema, bool removeExcessMappings)
        {
            //standard properties
            managedProperty.Description = description;
            managedProperty.EnabledForScoping = enabledForScoping;
            //Not writable
            try
            {
                managedProperty.FullTextQueriable = fullTextQueriable;
            }
            catch(InvalidOperationException)
            {
                //For some properties you can't set this value (even
                // to the existing value)
                //Ignore.
            }
            managedProperty.NameNormalized = nameNormalized;
            managedProperty.NoWordBreaker = noWordBreaker;
            managedProperty.RemoveDuplicates = removeDuplicates;
            managedProperty.RespectPriority = respectPriority;
            try
            {
                managedProperty.Retrievable = retrievable;
            }
            catch (InvalidOperationException)
            {
                //For some properties you can't set this value (even
                // to the existing value)
                //Ignore.
            }

            //ignore some special/system managed properties that we 
            // can't change anyway.
            if (!managedProperty.MappingDisallowed)
            {
                bool mappingsChanged = false;

                // Set the mappings
                MappingCollection mappingCollection = managedProperty.GetMappings();

                List<string> processedCrawledProperties = new List<string>();
                foreach (CrawledPropertyInfo inputCrawledProperty in CrawledProperties)
                {
                    Guid gPropSet = GetPropSetFromCrawlProperty(inputCrawledProperty, schema);
                    if (gPropSet != Guid.Empty)
                    {
                        processedCrawledProperties.Add(gPropSet + "|" + inputCrawledProperty.Name + "|" + inputCrawledProperty.VariantType);

                        //Does the mapping already exist?
                        if (!MappingContained(mappingCollection, gPropSet, inputCrawledProperty.Name, inputCrawledProperty.VariantType))
                        {
                            if( inputCrawledProperty.VariantType == 65 )
                            {
                                //Special handling of the SID type that apparently 
                                // can't be handled this way.
                                Trace.WriteLine("Skipped incompatible type 65 (leave untouched):" + inputCrawledProperty.Name);
                                continue;
                            }

                            mappingsChanged = true;

                            Mapping newMapping = new Mapping(
                                gPropSet,
                                inputCrawledProperty.Name,
                                inputCrawledProperty.VariantType,
                                managedProperty.PID);

                            mappingCollection.Add(newMapping);
                            Trace.WriteLine("Added Mapping to collection.");
                        }
                        else
                        {
                            //Trace.WriteLine("Skipped mapping of:" + Name + " to:" + inputCrawledProperty.Name);
                        }
                    }
                    else
                    {
                        Trace.WriteLine("Didn't find the Prop ID for " + inputCrawledProperty.Name, true);
                    }

                }

                //Remove excess mappings
                if (removeExcessMappings)
                {
                    List<Mapping> mappingsToRemove = new List<Mapping>();
                    foreach (Mapping mapping in mappingCollection)
                    {
                        if (!processedCrawledProperties.Contains(mapping.CrawledPropset + "|" + mapping.CrawledPropertyName + "|" + mapping.CrawledPropertyVariantType))
                        {
                            mappingsToRemove.Add(mapping);
                        }
                    }
                    foreach (Mapping mapping in mappingsToRemove)
                    {
                        Trace.WriteLine("Removing mapping, name:" + Name + " to:" + mapping.CrawledPropertyName);
                        mappingsChanged = true;
                        mappingCollection.Remove(mapping);
                    }
                }

                
                // Set the Mappings
                if ( mappingsChanged )
                {
                    managedProperty.SetMappings(mappingCollection);
                }
            }
            managedProperty.Update();
        }

        /// <summary>
        /// Search mapping collection for existing mapping
        /// </summary>
        /// <param name="mappingCollection"></param>
        /// <param name="gPropSet"></param>
        /// <returns></returns>
        private static bool MappingContained(MappingCollection mappingCollection, Guid gPropSet, string propName, int variantType)
        {
            foreach( Mapping m in mappingCollection )
            {
                if( m.CrawledPropset == gPropSet && 
                    m.CrawledPropertyName==propName &&
                    m.CrawledPropertyVariantType==variantType)
                {
                    return true;
                }
            }
            return false;            
        }


        public ManagedDataType GetManagedDataType()
        {
            return (ManagedDataType) Enum.Parse(typeof (ManagedDataType), managedType, true);
        }

        /// <summary>
        /// Get the PropSet from the Property.
        /// </summary>
        /// <param name="crawlPropertyInfo">Info (Name, id and type) of crawledproperty to search for</param>
        /// <param name="schema">Schema for the current SSP</param>
        /// <returns></returns>
        private static Guid GetPropSetFromCrawlProperty( CrawledPropertyInfo crawlPropertyInfo, Schema schema)
        {
            //First check if the property exists with the same propset id (i.e. it's
            // a "standard/system" crawled property).
            if( schema.GetCrawledProperty(crawlPropertyInfo.PropSet, crawlPropertyInfo.Name, crawlPropertyInfo.VariantType) != null )
            {
                return crawlPropertyInfo.PropSet;  
            }

            foreach (CrawledProperty searchedCrawledProperty in schema.QueryCrawledProperties(crawlPropertyInfo.Name, 1000, Guid.Empty, null, true))
            {
                // does the name match (search above is a "contains" search)?
                if (searchedCrawledProperty.Name == crawlPropertyInfo.Name)
                {
                    // yes - return the PROPSET
                    return searchedCrawledProperty.Propset;
                }
            }

            return Guid.Empty;
        }

    }
}
